const {Builder, By, Key, until} = require('selenium-webdriver')
const firefox = require('selenium-webdriver/firefox')
const {Origin} = require('selenium-webdriver/lib/input')
const seleniumProxy = require('selenium-webdriver/proxy');
const rewire = require('rewire')
const config = rewire('../config.cjs')

let driverMap = new Map()

async function createWebDriver(id) {
  const service = (new firefox.ServiceBuilder(config.binPath + '/chromedriver'))
  const driver = new Builder().forBrowser('chrome').setFirefoxService(service).build()
  driverMap.set(id, driver)
  return driver
}


async function createWebDriverFirfox(id, headless = false, proxy = false) {
  const service = (new firefox.ServiceBuilder(config.binPath + '/geckodriver'))
  let driver = null
  const options = new firefox.Options()
  options.setPreference('general.useragent.override', 'user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36')
  if (headless) {
    options.headless()
  }
  driver = new Builder()
    .forBrowser('firefox')
    .setFirefoxService(service)
    .setFirefoxOptions(options)

  if (proxy) {
    driver.setProxy(seleniumProxy.manual({http: proxy, https: proxy}))
  }

  driver = driver.build()

  driverMap.set(id, driver)
  return driver
}


async function driverGoto(driver, url) {
  await driver.get(url)
}


async function getWebDriver(id) {
  let driver = driverMap.get(id)
  if (driver) {
    return driver
  }
  return await createWebDriver(id)
}

async function getHtml(driver) {
  return await driver.getPageSource()
}


async function driverBack(driver) {
  await driver.navigate().back()
}

async function driverRefresh(client) {
  await driver.navigate().refresh()
}

async function driverSetCookies(driver, cookieStr) {
  const pies = cookieStr.split(";")
  for (let i = 0; i < pies.length; i++) {
    const item = pies[i]
    const kvPair = item.split("=")
    await driver.manage().addCookie({name: kvPair[0].trim(), value: kvPair.length == 2 ? kvPair[1].trim() : ""})
  }
}


async function elemFindItem(driver, css) {
  return await driver.wait(until.elementLocated(By.css(css)), 30000)
}

async function elemFindList(driver, css) {
  return await driver.wait(until.elementsLocated(By.css(css)), 30000)
}


async function elemLocation(elem) {
  return await elem.getRect()
}

async function elemGetItem(driver, css) {
  return await driver.findElement(By.css(css))
}

async function elemGetList(driver, css) {
  return await driver.findElements(By.css(css))
}

async function elemGetChildrenList(elem, css) {
  return await elem.findElements(By.css(css))
}

async function elemGetChildrenItem(elem, css) {
  return await elem.findElement(By.css(css))
}

async function elemGetValue(elem) {
  return await elem.getAttribute('value')
}

async function elemGetText(elem) {
  return await elem.getText()
}

async function elemExists(driver, css) {
  const found = await driver.findElements(By.id(css))
  return !!found.length
}

async function elemClick(elem) {
  return await elem.click()
}

async function elemInput(elem, text) {
  await elem.sendKeys(text)
}

async function elemInputSet(elem, text) {
  while (await elemGetValue(elem) != '') {
    await elem.clear()
  }
  await elem.sendKeys(text)
}

async function elemGetHtml(elem) {
  await elem.getAttribute('outerHTML')
}

async function elemWaitUtil(driver, css, fn, errormsg = '执行超时') {
  await driver.wait(async function() {
    const elem = await elemGetItem(driver, css, errormsg)
    return await fn(elem)
  }, 30000)
}

async function elemHasClass(elem, cls) {
  const classVal = await elem.getAttribute('class')
  return classVal.includes(cls)
}

async function elemHasAttr(elem, name, val) {
  const attrVal = await elem.getAttribute(name)
  return attrVal === val
}

async function driverWaitUtil(client, fun, msg) {
  await client.wait(fun, 30000, msg)
}

async function cssClick(client, css) {
  const elm = await elemFindItem(client, css)
  await elemClick(elm)
}

async function driverClick(client, x, y) {
  await client.actions().move({x, y}).pause(300).press().release().perform()
}

async function cssInput(client, css, text) {
  const elm = await elemFindItem(client, css)
  await elemInput(elm, text)
}

async function cssGetText(client, css) {
  const elm = await elemFindItem(client, css)
  return await elemGetText(elm)
}

async function cssClickIfExists(client, css) {
  const is = await elemExists(client, css)
  if (is) {
    await cssClick(client, css)
  }
}


async function driverScollPage(driver) {
  await driver.actions().keyDown(Key.PAGE_DOWN).perform()
}

async function driverSendString(driver, s) {
  await driver.actions().sendKeys(s).perform()
}

async function driverWaitTime(driver, ms) {
  await driver.sleep(ms)
}

async function switchTab(driver, num) {
  await driver.wait(async () => (await driver.getAllWindowHandles()).length >= num, 30000)
  const windows = await driver.getAllWindowHandles()
  await driver.switchTo().window(windows[num - 1])
}

async function closeCurrentTab(driver) {
  await driver.close()
  const windows = await driver.getAllWindowHandles()
  await driver.switchTo().window(windows[0])
}

async function closeTab(driver, num) {
  const originalWindow = await driver.getWindowHandle()
  await switchTab(driver, num)
  await driver.close()
  const windows = await driver.getAllWindowHandles()
  for (let win of windows) {
    if (win == originalWindow) {
      await driver.switchTo().window(win)
      return
    }
  }
  await driver.switchTo().window(windows[0])
}

async function sendEnter(driver, dom = null) {
  if (dom) {
    await dom.sendKeys(Key.ENTER)
  }
  else {
    await driver.actions().sendKeys(Key.ENTER).perform()
  }
}


module.exports = {createWebDriver, createWebDriverFirfox, getWebDriver, driverGoto, driverBack, driverRefresh, driverSetCookies, elemFindItem, elemFindList, elemLocation, elemGetItem, elemGetList, elemGetValue, elemGetText, elemExists, elemClick, elemInput, elemInputSet, elemGetHtml, elemWaitUtil, driverWaitUtil, driverScollPage, driverSendString, driverWaitTime, switchTab, closeCurrentTab, closeTab, cssClick, cssInput, cssGetText, driverClick, elemGetChildrenList, elemGetChildrenItem, cssClickIfExists, elemHasClass, sendEnter, elemHasAttr, getHtml}
